/*
 * Copyright 2010 Jeroen Brosens
 *    
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

$(document).ready(function() {
	new Camera();
});

// GLOBAL METHODS

function initObjects3D() {
	var o = $(".object3d");
	var o3dArray = new Array();

	for ( var i = 0; i < o.length; i++) {
		var object3d = new Object3D(o[i]);

		object3d.jq_object.text('z=' + object3d.pos.z);
		o3dArray.push(object3d);
	}
	
	var tn = createThickness(o3dArray);
	for (var i = 0; i < tn.length; i++) {
		o3dArray.push(tn[i]);
	}
	return o3dArray;
}

function initViewport(camera) {
	var jq_viewport = $("#viewport");

	jq_viewport.bind('mousewheel', function(event, delta) {
		var d = delta * 25;
		if (camera.focalLength + d >= 0) {
			camera.focalLength += d;
		}
		camera.update();
	});

	jq_viewport.bind('mousemove', function(e) {
		camera.pos.x = e.clientX;
		camera.pos.y = e.clientY;

		camera.update();
	});

	$(document).keypress(function(e) {
		switch (e.charCode) {
		case 119: // W
				camera.move(new Vector3D(0, 0, -10));
				camera.update();
				break;
			case 115: // S
				camera.move(new Vector3D(0, 0, 10));
				camera.update();
				break;
			}
		});

	createStatusPanel(jq_viewport[0]);

	return jq_viewport;
}

function createStatusPanel(element) {
	var status = document.createElement('div');
	status.setAttribute('id', 'status');
	status.setAttribute('style', 'position: absolute; z-index: 99999');
	element.appendChild(status);
}

function updateStatus(text) {
	$("#status").html(text);
}

// TYPES

/**
 * @constructor
 */
function Vector3D(x, y, z) {
	this.x = x;
	this.y = y;
	this.z = z;
}

/**
 * @constructor
 */
function Object3D(el) {
	this.jq_object = $(el);

	this.initialWidth = this.jq_object.width();
	this.initialHeight = this.jq_object.height();
	this.initialFontSize = parseFloat(this.jq_object.css('font-size'));
	
	this.pos = new Vector3D(
		this.jq_object.position().left, 
		this.jq_object.position().top, 
		parseInt(this.jq_object.css("z-index"), 10));
}

function createThickness(object3ds) {
	var n = 5;
	var tnArray = new Array();
	
	for (var i = 0; i < object3ds.length; i++) {
		var o = object3ds[i];
		if (o.jq_object.hasClass('thickness')) {			
			for (var j = n; j > 0; j--) {
				var id = o.jq_object.attr("id") + "_thickness" + j;
				var z = o.pos.z - j;
				var tn = $(document.createElement('div'))
					.attr("id", id)
					.addClass("object3d thickness")
					.css("z-index", z)
					.css("width", o.initialWidth)
					.css("height", o.initialHeight)
					.css("top", o.pos.y)
					.css("left", o.pos.x)
					.insertAfter(o.jq_object);
				
				tnArray.push(new Object3D($(tn)));
			}
		}
	}
	return tnArray;
}

/**
 * @constructor
 */
function Camera() {
	this.jq_viewport = initViewport(this);
	this.targets = initObjects3D();
	this.diameter = 36;
	this.focalLength = 200;
	this.pos = new Vector3D(this.jq_viewport.position().left, this.jq_viewport
			.position().top, parseInt(this.jq_viewport.css("z-index"), 10));
}

Camera.prototype.distance = function() {
	return parseInt(this.pos.z, 10);
}

Camera.prototype.fov = function() {
	return 2 * Math.atan2(this.diameter / 2, this.focalLength);
}

Camera.prototype.displacement = function() {
	return new Vector3D(this.pos.x - this.jq_viewport.width() / 2, this.pos.y
			- this.jq_viewport.height() / 2, 0);
}

Camera.prototype.project = function(vector) {
	var scale = this.focalLength / (this.distance() - vector.z);
	var d = this.displacement();

	return new Vector3D(scale * (vector.x - d.x), scale * (vector.y - d.y),
			this.pos.z);
}

Camera.prototype.move = function(vector) {
	this.pos.x += vector.x;
	this.pos.y += vector.y;
	this.pos.z += vector.z;
}

Camera.prototype.update = function() {
	for (var i = 0; i < this.targets.length; i++) {
		var jq_target = this.targets[i];
		var t = jq_target.jq_object;
		
		// Perform Z-clipping
		if (this.pos.z < jq_target.pos.z) {
			t.css("display", "none");
		} else {
			t.css("display", "block");
			
			var pTopLeft = this.project(jq_target.pos);
			var pBottomRight = this.project(new Vector3D(jq_target.pos.x
					+ jq_target.initialWidth, jq_target.pos.y
					+ jq_target.initialHeight, jq_target.pos.z));
	
			// Update target locations
			t.css("left", this.jq_viewport.width() / 2 + pTopLeft.x);
			t.css("top", this.jq_viewport.height() / 2 + pTopLeft.y);
	
			if (!t.hasClass('fixedsize')) {
				// Update target sizes
				t.width(pBottomRight.x - pTopLeft.x);
				t.height(pBottomRight.y - pTopLeft.y);
	
				// Update font sizes
				var fs = jq_target.initialFontSize * t.width()
						/ jq_target.initialWidth;
				t.css('font-size', fs);
			}
		}
	}

	// Update status
	updateStatus(this.toHTML());
}

Camera.prototype.toHTML = function() {
	return 'Z: ' + (this.pos.z) + '<br>Focal length: ' + this.focalLength
			+ 'mm<br>FOV: ' + Math.round(180 / Math.PI * this.fov()) + 'deg';
}
